この章では C および Bison と一緒に Flex を使う方法を説明します。C、Bison のそれぞれが非常に多くの細目を含むため、本章は2つの部分に分割されています。それぞれの章において、全般的なインターフェイス概念に関する節と実例を示す節が提供されます。
Flex に対する C の主要なインターフェイスは、以下に挙げるルーチンと変数によるものです。以下の節を読む際には、いくつかの細かな部分で
Flex と Lex との間に相違点があるということを意識しておいてください。Lex
が提供していない関数がいくつかありますし、宣言の内容が違うものもあります。これらは通常大きな問題にはなりません。というのは、相違のある関数は一般的にはあまり使われていないからです。相違点に関する詳細については、***ページの
8 章「Flex と Lex」および***ページの 8.1.1
節「Flex と POSIX」を参照してください。
関数 | 説明と実例 |
---|---|
yylex() | yylex() が実際のスキャン処理を行う関数です。これは、ファイル(デフォルトは
stdin)を読み込み、パターン・マッチングを行い、パターンに関連付けされたアクションを実行します。デフォルトでは、入力の終端に達するまでマッチングを行い、終端に達したところでゼロを返します(
return を使って、呼び出し側のプログラムに他の値を返すことは可能です。これは、***ページの
4.3 節「Flex と Bison」で説明されています)。したがって、インターフェイスを提供する簡単な方法の1つは、オプションの
C コード領域の1つに以下のようなコードを追加することです。
int main(argc, argv)
yylex() の使い方としてもう1つよく見られるのが、マッチされたものが何であるかを示す値を返させることです。これはアクションに return 文を追加することで行われます。return 文を見つけると yylex() は指定された値を返します。これが、Bison によるパーサが Flex によるスキャナから情報を獲得する方法です。 マッチされたテキストが何を表しているかを示すコードを返す return 文をルールが持っているのであれば、以下のようなインターフェイスを使うことができます。
int main(argc, argv)
while((return_code = yylex()) != 0){ switch(return_code){
break;
break; ... }
#define YY_DECL char *gettok(char *buffer)
|
yyin | yyin は、yylex() がそこから文字を読み込むファイルです。デフォルトは
stdin ですが、fopen() を使って変更することができます。yyin を読み込むデフォルトの方法は、複数文字から成るブロックを一度に読むというものです。これは
YY_INPUT マクロによって変更できます。YY_INPUT マクロは、ファイルではなく文字列をスキャンするためのスキャナを生成するのに便利です。YY_INPUT
を定義する方法は以下のとおりです。
#define YY_INPUT(buffer,result,max_size) \
if(buffer[0] == EOF)\
|
yyout | yyout はスキャナが ECHO の出力を書き込むファイルです。デフォルトは
stdout ですが、これも fopen() を呼ぶことで変更できます。
|
yytext | yytext は最後にマッチされた文字列、つまり最後に認識されたトークンを含む大域変数です。yytext
の正しい外部定義は、Lex の場合の char の配列とは異なり、char に対するポインタ型である点に注意してください。つまり、yytext
は
通常は yytext は変更すべきではありません。yytext の内容が変更される必要がある場合には、代わりのバッファが使われるべきです(examples
サブディレクトリの yymore2.lex ファイルでは yytext を直接操作する技法が実演されています。ただし、このようなやり方はお薦めできません)。
|
yyleng | yyleng は最後に認識されたトークンの長さを保持する大域変数です。
|
yywrap() | yywrap は yyin の終端に達した時に呼ばれる関数です。この関数が TRUE(非ゼロ)を返すとスキャナは終了し、FALSE(ゼロ)を返すと
yyin が次の入力ファイルを指すように設定されたものと仮定してスキャン処理が続けられます。
現在のところ yywrap() は、常に1を返すよう定義されているマクロです。そのため、これを再定義するには、まず最初にそれを
#undef で定義解除しなければなりません。Lex では yywrap() は本物の関数です。Flex
も将来のある時点でこれを関数として定義することになるでしょう。
|
yymore() | yymore() は、次に認識されるトークンで yytext の内容を更新するのではなく、その時点の
yytext の内容のうしろにそのトークンを追加するよう Flex に通知する関数です。したがって、以下の例にたいして
foobar という文字の並びを入力として与えると、stdout に foofoobar という文字の並びが書き込まれます。
foo ECHO; yymore(); bar ECHO; もう少し現実的な例を取り上げましょう。以下のコードは複数行の文字列を処理するのに yymore() を使っています。
* yymore.lex: yymore() を有効に使う例 */ %{
%} %x STRING %%
<STRING>[^\\\n"]* yymore();
BEGIN(INITIAL);
BEGIN(INITIAL);
*/
printf("string = \"%s\"",yytext); BEGIN(INITIAL); |
yyless(n) | yyless() は yymore() とほぼ反対のことを行うものです。この関数は、最初の
n 文字以外のすべてを返します。返された文字の並びは、次のトークンをマッチするのに使われ、yyleng
と yytext にはこの変化を反映した値が設定されます。yyless() を引数 n
にゼロを指定して呼び出すと、全入力データが返され、スキャナは(BEGIN もしくはそれに類似のものでデフォルトの動作が変更されない限り)無限ループに入ります。例えば、次のコードに
foobar という文字の並びを入力として与えると、foobarbar という文字の並びが出力されます。
foobar ECHO; yyless(3); [a-z]+ ECHO; |
input() | input() は yyin から次の文字を取って返す関数です。これは、標準的な
Flex ルール・システムを使ったのではきれいに扱えない特定のケースを処理するのによく使われます。例えば、ほとんどの言語におけるコメントはこれを使って処理することができます。これを使う理由は、
"/*".*"*/"
"/*"[.\n]*"*/"
"/*" {
a = input();
if(a == '*' && b == '/'){
if(a == EOF){
|
unput(c) | unput() は、文字 c が次にスキャンされる文字になるように、文字
c を入力ストリームに置く関数です。例えば、
foo unput('b'); 1つの文字が次にスキャンされる文字になるということには1つ微妙な点があって、それは、文字列を入力ストリームに置きたい場合には逆順に行わなければならないということです。以下に例を示します。
int i = strlen(baz)-1; while(i >= 0){
i--;
* unput.l : unput() を使って行ってはならない処理の例 */ %{
void putback_yytext(void);
%%
void putback_yytext(void)
int l = strlen(yytext); char buffer[YY_BUF_SIZE]; strcpy(buffer,yytext);
注:input() と同様に unput() も yytext の内容を破壊します。このことは、yytext
から文字情報を返したい場合には、上の例に示されるように、まず yytext の内容をコピーしなければならないことを意味しています。
|
yyterminate() | アクションの中で呼ばれると、yyterminate() はスキャナの実行を終了させ、その後に
yylex() が 0 を返します。この後は、yyrestart()(下記参照)が呼ばれない限り、yylex()
を呼んでもすぐに復帰してしまいます。
|
yyrestart(file) | yyrestart() はスキャナの実行を再開するよう Flex に通知する関数です。これは引数を1つだけ取り、それはスキャンの対象となるファイル(通常は
yyin)です。これは、EOF を処理するために使うこともできますし、また、Flex
に割り込みをかけ、その後に再開始させることができるようにするために使うこともできます(Flex
スキャナは再入可能ではないので、このようなことが必要になります)。
|
YY_NEW_FILE | yyin が新しいファイルを指すよう変更され、処理が継続されるべきであるということを
Flex に通知するマクロです。例を以下に示します。
* cat.lex: YY_NEW_FILE の実演 */ %{
char **names = NULL;
%%
if(names[current] != NULL){
if(yyin == NULL){
yyterminate(); YY_NEW_FILE;
int main(int argc, char **argv)
exit(1); names = argv; yyin = fopen(names[current],"r");
yyterminate(); yylex(); |
ECHO | yytext の内容を yyout に書き込むマクロです。
|
REJECT | REJECT は、その時点においてマッチしているものを受け入れないで、次に最もよくマッチするものを受け入れるようスキャナに通知するマクロです。スキャナはマッチするものの中で最長のものを探し、マッチするものが2つあってその長さが同じ場合は、記述ファイルにおいて最初に定義されている方を選択します。このことは、認識されるテキストの長さは、同一の長さになることもあり、あるいはより短かくなることもあるということを意味しています。REJECT
を使った後は、yytext と yyleng は新しい値を取ります。REJECT に関して知っておくべき重要な点が2つあります。1つめは、REJECT
は分岐命令であり、決っして戻ってこないので、REJECT のうしろに記述されたアクションは実行されないということです。2つめは、REJECT
とファスト・テーブル(fast tables)(-F)は一緒に使うことはできないということです。以下に簡単な例を示します。
* reject.lex: REJECT と unput() を悪用する実例 */ %%
unput('G'); unput('\0'); REJECT; %%
GNU is Not Unix!
nmonth [1-9]|1[0-2] nyear [0-9]{1,4} %x DAY MONTH YEAR
{nday}
BEGIN(DAY); REJECT;
<MONTH>{nday}
<YEAR>{nday}
|
BEGIN | BEGIN はスキャナをある特定のスタート状態にするためのマクロです。BEGIN
に続く名前はスタート状態の名前です。例えば、
%% float BEGIN(FLOAT) <FLOAT>some_rule some_action ... |
YY_USER_ACTION | YY_USER_ACTION はルール・セクション中のどのアクションよりも前に実行されるアクションを定義するマクロです。これは、以下の例で示すように、yytext
の内容の小文字から大文字への変換等を行うのに役に立ちます。
* user_act.lex: YY_USER_ACTION を使うユーザ・アクションの例 */ %{ #include <ctype.h> void user_action(void); #define YY_USER_ACTION user_action(); %} %% .* ECHO;
%% /*
void user_action(void)
for(loop=0; loop<yyleng; loop++){
|
YY_USER_INIT | YY_USER_INIT は、スキャン処理が開始される前に実行されるアクションを定義するマクロです。これは基本的には
main() 関数の中で yylex() を呼び出す文の前に同様のコードを記述するのと同じことです。以下に簡単な例を示します。
* userinit.lex: YY_USER_INIT を使う例 */ %{
extern FILE *yyin; void open_input_file(void)
yyin = NULL; while(yyin == NULL){
file_name = fgets(buffer,1024,stdin); if(file_name){
yyin = fopen(file_name,"r"); if(yyin == NULL){
file_name);
yyin = stdin; break; %}
* この例は前の例と同じことを YY_USER_INIT を使わずに行う */ %{
yyin = NULL: while(yyin == NULL){
file_name = fgets(buffer,1024,stdin); if(file_name){
yyin = fopen(file_name,"r"); if(yyin == NULL){
file_name);
yyin = stdin; break; %}
int main(int argc, char *argv[])
yylex(); |
YY_BREAK | YY_BREAK はマクロです。これは、インターフェイス的な機能ではなく、むしろ生成されるコードを変更するために使うことができるものです。
スキャナ中においてすべてのアクションは1つの大きな switch 文の要素であり、それらはデフォルトで C の break; 文に置き換えられる YY_BREAK によって区切られます。ルールのアクション部が多くの return 文を含んでいる場合、コンパイラが statement not reached というエラー・メッセージをたくさん出力するかもしれません。YY_BREAK を再定義することによって、この警告メッセージの出力を止めることが可能です。再定義は、セミ・コロンを含む正当な C の文でなければなりません。 注:YY_BREAK を再定義して空にするのであれば、アクションの最後は必ず return; か break; になるようにしてください。 |
ある単語が現れた時にそれを別の単語に置き換える必要のあることがよくあります。例えば、ある名前が現れるたびにそれをある1つの環境変数の値で置き換えてくれるユーティリティを作りたいとしましょう。そして、以下のようなことができるように、そのユーティリティがフィルターとして動作するようにさせたいとします。
%%
%NAME
{ printf("%s",getenv("LOGNAME")); }
%HOST
{ printf("%s",getenv("HOST")); }
%HOSTTYPE { printf("%s",getenv("HOSTTYPE"));
}
%HOME
{ printf("%s",getenv("HOME")); }
%%
いずれの場合でも、最終的には myname という名前の実行ファイルが生成されるはずです。これは、以下のような変換処理を実行するフィルタです。
%NAME | ユーザのログイン名に置き換えられます。
|
%HOST | ユーザのホスト・コンピュータ名に置き換えられます。
|
%HOSTTYPE | ユーザのホスト・コンピュータのマシン・タイプに置き換えられます。
|
%HOME | ユーザのホーム・ディレクトリを表すパスに置き換えられます。
|
これが、引用符で囲まれた部分にあるものも含めて、指定された名前が現れるところすべてにマッチしたことに気がつきましたか? Flex においては、引用符で囲まれた部分にあるものにマッチさせたくない場合には、それに対応するルールを作成することにより、そうしないよう明示的に Flex に通知しなければなりません。以下に例を示します。
%{
#include <stdio.h>
%}
%x STRING
%%
\"
ECHO; BEGIN(STRING);
<STRING>[^\"\n]* ECHO;
<STRING>\"
ECHO; BEGIN(INITIAL);
%NAME
{ printf("%s",getenv("LOGNAME")); }
%HOST
{ printf("%s",getenv("HOST")); }
%HOSTTYPE { printf("%s",getenv("HOSTTYPE"));
}
%HOME
{ printf("%s",getenv("HOME")); }
Bison は、Flex と同様、ある記述情報を受け取って、それをもとに C のコードを生成するプログラムです。両者の違いは、Bison が C や Pascal のような言語の文法に関する記述情報を入力として受け取り、その記述情報からパーサを生成する点にあります。Flex と Bison を結合することにより、言語の字句解析と構文解析の両方を処理することができるようになります(これらはコンパイラ・デザインにおいて最も容易に自動化できる部分です)。
生成されるパーサが機能するためには、Bison は yylex() という関数を必要とします。この関数はユーザによって提供され、呼び出された時に、パースされている言語のある要素を表わす整数値を Bison に返します。Flex においてスキャン処理を行うルーチンは yylex() であり、これはデフォルトでは整数値を返します。これにより、Flex と Bison を一緒に使うのは非常に簡単になります。
注:以下の節では、読者が Bison の基本的なパーサの宣言を理解しているものと仮定します。Bison を使った経験のない人には、パーサの定義は混乱をもたらす可能性がありますので、先に進む前に是非 Bison マニュアルを読んでください。Bison に興味の無い人は、この節全体をスキップしても構いません。
Flex と Bison の間で情報を渡す基本的な方法は、関数 yylex() を使うことです。これは、Flex により生成されるスキャナにおいてスキャン処理を実行する関数の名前です。Flex の入力ファイルのアクション部分において return 文を使うことによって、単なる 0 や 1 以外の値を返すことができます。これにより、最後に認識されたトークンを表わす整数値を yylex() は返すことができます。
Bison を -d オプション付きで使うと、Bison は .tab.h という拡張子を持つファイルを生成します。このファイルには、記述情報中にある正当なトークンの1つ1つに対する一意な定義情報が含まれます。この出力情報は、特にスキャナによって使用されることを想定して設計されています。このファイルを Flex により生成されたスキャナに含めることで、2つのプログラムの間に非常に明確なインターフェイスを生成することができます。例として、以下に Bison のファイルを示します。このファイルの名前を expr.y としましょう。
%{
#include <stdio.h>
#include <math.h>
%}
%union {
%token NUMBER
%token PLUS MINUS MULT DIV EXPON
%token EOL
%token LB RB
%left MINUS PLUS
%left MULT DIV
%right EXPON
%type <val> exp NUMBER
%%
input :
void yyerror(char *s)
{
int main()
{
-y -d オプション付きで呼び出されると、Bison は y.tab.h というファイルを生成します。このファイルには以下のような定義か、それに極めてよく似た定義が含まれます。
%{
#include "y.tab.h"
%}
%%
[0-9]+ { yylval.val = atof(yytext);
この例では、Flex と Bison の間で情報を渡すための別の方法を導入していることに注意してください。この例では、数字の値を Bison に返すのに yylval を使っています。これについては次の節でより詳細に説明します。ここではとりあえず、return 文の使い方を学んでおいてください。
注:これは単純な例です。表現式のパース処理についてより詳しく知りたい人は、Bison マニュアルを参照してください。
Flex から Bison に対して、単なる整数値以上の情報を渡す必要のあることがよくあります。例えば、コンパイラにおいては、どのようなトークンが認識されたかだけではなく、そのトークンの値についても知る必要のある場合がときどきあります。文字列、文字、数値定数などがこの良い例です。ここで問題なのは、どのようにして Flex にこうした情報を返させるかです。
その答えは Bison が持っている %union 文で、これは YYSTYPE という型を定義するものです。YYSTYPE は、パーサ定義中において使われるすべての正当なデータ型の共用体です。Bison がカレントなパース状態に関連づけたデータを保存するために使う、YYSTYPE 型の変数 yylval というものがあります。Flex から yylval に値を設定することができるので、トークンの型だけでなく、それ以上の情報を Bison に返すことができます。
Bison において %union を宣言して -d オプションを使うと、Bison は .tab.h という拡張子を持つファイルを作成して、そこにトークンの定義情報だけでなく、YYSTYPE と yylval の宣言も含めます。したがって yylval にアクセスするためにしなければならないことは、Flex の定義情報の中にこの .tab.h ファイルをインクルードすることだけです。これは、追加の C コード・セクションにおける定義の先頭でインクルードしなければなりません(***ページの 4.3.1 節「Flex と Bison のインターフェイス」を参照)。
注:初期のバージョンの Bison は自動的に YYSTYPE と yylval の宣言を生成しません。この場合には、より新しいバージョンの Bison を入手するか、もしくは、Flex の定義ファイルの先頭において YYSTYPE と yylval を宣言する必要があります。
コードを読むのはプログラミングの方法を学ぶ良い方法です。そこで、Flex、Bison のインターフェイス例をもう1つ示すことにします。下の例では、拡張してデータベースを操作するために使うことができるような小規模な言語のための簡単なパーサを構築します。
データベースとのインターフェイス言語は英語の非常に小さなサブセットになります。文法はおおよそ以下のようになります。
上の節で、小規模な言語について説明しました。次にそれを実装してみることにしましょう。以下のファイルがこれを実現します。
注:これはあくまでも1つの例として見てください。特に文法の部分は、英語のパース処理としてはあまり良い例ではありません。
以下は Bison のファイルです。%union の部分、および、yylval にアクセスするために $$ と $n を使う方法に注目してください。
%{
#include <stdio.h>
#include <string.h>
extern int yylexlinenum; /* これらは lex.yy.c に存在する */
extern char *yytext; /* カレントなトークン */
%}
/* キーワードと予約語がここから始まる */
%union{ /* これはデータの共用体 */
/*------------- 予約語 ------------------/
%token PERIOD
%token NEWLINE
%token POSITIONAL
%token VERB
%token ADVERB
%token PROPER_NOUN
%token NOUN
%token DECLARATIVE
%token CONDITIONAL
%type <name> declarative
%type <name> verb_phrase
%type <name> noun_phrase
%type <name> position_phrase
%type <name> adverb
%type <name> POSITIONAL VERB ADVERB PROPER_NOUN
%type <name> NOUN DECLARATIVE CONDITIONAL
%%
sentence_list : sentence
/* main() および yyerror() 関数を提供する */
void main(int argc, char **argv)
{
int yyerror(char *message)
{
fprintf(yyout,"\nError at line %5d. (%s) \n",
yylexlinenum,message);
#define TRUE 1
#define FALSE 0
#define copy_and_return(token_type)\
%%
/* 字句解析ルールがここから始まる */
MEN|WOMEN|STOCKS|TREES
copy_and_return(NOUN)
MISTAKES|GNUS|EMPLOYEES copy_and_return(NOUN)
LOSERS|USERS|CARS|WINDOWS copy_and_return(NOUN)
DATABASE|NETWORK|FSF|GNU copy_and_return(PROPER_NOUN)
COMPANY|HOUSE|OFFICE|LPF copy_and_return(PROPER_NOUN)
THE|THIS|THAT|THOSE copy_and_return(DECLARATIVE)
ALL|FIRST|LAST copy_and_return(CONDITIONAL)
FIND|SEARCH|SORT|ERASE|KILL copy_and_return(VERB)
ADD|REMOVE|DELETE|PRINT
copy_and_return(VERB)
QUICKLY|SLOWLY|CAREFULLY copy_and_return(ADVERB)
IN|AT|ON|AROUND|INSIDE|ON copy_and_return(POSITIONAL)
"."
return(PERIOD);
"\n"
yylexlinenum++; return(NEWLINE);
.
%%
注:Bison パーサは alloca.c というファイルを必要とします。このファイルは examples サブ・ディレクトリにあります。Bison の代わりに yacc を使うのであれば、このファイルは必要ありません。
以下に実装に関する注を示します。
yylval が Flex からアクセスされる方法に注目してください。Bison 文法においてパース・ツリーの上位にデータを渡す方法については
Bison マニュアルに説明されていますが、Flex に対しては何の影響も持ちません。整数値、浮動小数点数値、および他の任意の型のデータも同様の方法で返すことができます。
この例では、トークンの型と値の両方が Bison からアクセスできるように、トークンの値と文字列の値の両方が
Bison に返されていることに注意してください。
Bison と Flex がいかにうまく調和しているかに注意してください。データを交換するためのコード以外にインターフェイスのためのコードは一切ありません。Bison は yylex() を呼び出し、スキャナがトークン定義を提供しています。
Copyright (C) 1992, 1993 Free Software Foundation
Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.
Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.
Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.
日本語訳:市川和久
Japanese translation by Kazuhisa Ichikawa (ki@home.email.ne.jp)